﻿module BigIntCalc

open System.Numerics

open FsFlow

type Operation =
    | Add = 0
    | Subs = 1
    | Mult = 2
    | Div = 3

type ResultOrError =
    | ErrorMessage of string
    | Result of bigint
    | DivResult of bigint * bigint

type ViewModel = 
    {
        Arg1    : string Published
        Arg2    : string Published
        Op      : Operation PublishedEnum
        ErrOrEq : string Published
        Result  : string Published
    }

let tryParseBigInt s =
    match BigInteger.TryParse s with
    | true, res -> Some res
    | _ -> None    

let createViewModel() =
    let arg1 = Flow.var "0"
    let arg2 = Flow.var "0"

    let arg1num = arg1 |> Flow.map tryParseBigInt
    let arg2num = arg2 |> Flow.map tryParseBigInt

    let op = Flow.var Operation.Add

    let resComp =
        comp {
            let! a1, a2 = arg1num, arg2num 
            match a1, a2 with
            | None, None -> return ErrorMessage "Arguments 1 and 2 are not valid integers."
            | None, _ -> return ErrorMessage "Argument 1 is not a valid integer."
            | _, None -> return ErrorMessage "Argument 2 is not a valid integer."
            | Some a1v, Some a2v ->
                let! o = op
                match o with
                | Operation.Add -> return Result (a1v + a2v)
                | Operation.Subs -> return Result (a1v - a2v)
                | Operation.Mult -> return Result (a1v * a2v)
                | Operation.Div -> return if a2v = 0I then ErrorMessage "Cannot divide by 0." else DivResult (a1v / a2v, a1v % a2v)
                | _ -> ()
        }

    let errOrEq = resComp |> Flow.map (function ErrorMessage err -> err | _ -> "=")
    let result =  resComp |> Flow.map (function Result res -> string res | DivResult (res, rem) -> sprintf "%A (rem = %A)" res rem | _ -> "") 

    {
        Arg1    = arg1 |> Flow.publish
        Arg2    = arg2 |> Flow.publish
        Op      = op |> Flow.publishEnum
        ErrOrEq = errOrEq |> Flow.publish
        Result  = result |> Flow.publish
    }